/* Since http is stateless, we need a way to track users
   from one page to the next.

   If a user does not have a GOSESSIONID cookie, we create one and use that id as a key for storage and retrieval of session data 
*/

package util

import (
	"fmt"
    "strconv"
    "strings"
    "time"
    "utils/web"
    "utils/mgo"
    "utils/mgo/bson"

	//godis "github.com/simonz05/godis/redis"

)

type SessionStore interface {
    //Set(key string, val string, expire int64) os.Error
    //Get(key string) (string, os.Error)
    Set(key string, val string) string  //by indraw 2013.02
    Get(key string) (string, string)
    Delete(keys ...string)
    Remove(id string)

}

type Session struct {
    ctx *web.Context
    store SessionStore
    timeout int64
    expiration int64
    
    //by indraw 解决第一次访问为空问题
    sessionid string
}

func createKey(vals ...string) string {
	return strings.Join(vals, ":")
}

func NewSession(ctx *web.Context, store SessionStore) *Session {
	// create a session with a default timeout of 1 hour and
	// a default expiration of session cookies of 1 day
	return &Session{ctx: ctx, 
					store: store, 
					timeout: 60*60,
					expiration: 60*60*24,
					sessionid:""}
}

func (s *Session) generateSessionID() (string, bool) {
	// FIXME: temporary. Should probably be more secure
	//id := strconv.Itoa64(time.Nanoseconds())
    if s.sessionid != "" {
		return s.sessionid,true
		//fmt.Printf("session1:%s\n" , s.sessionid)

	}
	
	id := strconv.FormatInt(time.Now().UnixNano(),10) //by indraw
	s.sessionid = id
	
	//fmt.Printf("session2:%s\n" , id)

	ok := true
	return id, ok
}

func (s *Session) getSessionID() (string, bool) {

    id, ok := s.ctx.GetSecureCookie("GOSESSIONID")

    return id, ok
}

func (s *Session) setSessionID(id string) {
	s.ctx.SetSecureCookie("GOSESSIONID", id, s.expiration)
	//creationTime := strconv.Itoa64(time.Seconds())
    creationTime := strconv.FormatInt(time.Now().Unix(),10) //by indraw
	// Set the creation time
	key := createKey(id)
	val := string([]uint8(creationTime))
	s.store.Set(key, val)
	// Set the last activity time
	key = createKey(id, "s_time")
	// set the last activity time
	s.store.Set(key, val)
}

func (s *Session) removeSessionID() {
	id, ok := s.getSessionID()
	if !ok {
		/* it doesn't exist to delete, or the user cleared cookies 
		   and it'll just expire and be removed eventually 
		*/
		return
	}
	// remove the session entries corresponding to the ID
	s.store.Remove(id)
	// set the session cookie to expire immediately
	s.ctx.SetSecureCookie("GOSESSIONID", "", -1)
}

func (s *Session) updateLastActivityTime() {
	id, ok := s.getSessionID()
	if !ok {
		return
	}
	key := createKey(id, "s_time")
	//curTime := string([]uint8(strconv.Itoa64(time.Seconds())))
	curTime := string([]uint8(strconv.FormatInt(time.Now().Unix(),10))) //by indraw
    
	s.store.Set(key, curTime)
}

func (s *Session) isTimedOut() (expired bool) {
	id, ok := s.getSessionID()
	if !ok {
		return true
	}
	key := createKey(id, "s_time")
	lastActivity, err := s.store.Get(key)
	if err != "" {
		return true
	}
	//fmt.Println("-mongo3:"+lastActivity)

	//lastActivityTime, err := strconv.Atoi64(lastActivity)  
    lastActivityTime, err2 := strconv.ParseInt(lastActivity,10,64)  //by indraw
	if err2 != nil {
		return true
	}
	if lastActivityTime + s.timeout < time.Now().Unix() {
		return true
	}
	return false
}
/*
func (s *Session) getStoredKeys() []string {
	id, ok := s.getSessionID()
	if !ok {
		return []string{}
	}
	key := createKey(id, "s_keys")
	storedKeys, err := s.store.Get(key)
	if err != "" {     // nil to ""
		return []string{}
	}
	return strings.Split(storedKeys, ":")
}
**/
/*
func (s *Session) updateStoredKeys(newKey string) {
	id, ok := s.getSessionID()
	if !ok {
		return
	}
	key := createKey(id, "s_keys")
    storedKeys, err := s.store.Get(key)
    if err != "" {  // nil to ""
    	// this is the first stored key
    	s.store.Set(key, newKey, s.expiration)
    } else {
    	// add it to the 'list' of keys stored already
    	s.store.Set(key, storedKeys + ":" + newKey, s.expiration)
    }
}
*/
func (s *Session) Set(key string, val string) (success bool) {
	// try to find a current session
    id, ok := s.getSessionID()
	if !ok {
		// if it doesn't exist, create one
		id, ok = s.generateSessionID()
		if !ok {
			return false
		}
		// set it as the new session
		s.setSessionID(id)
	} else if s.isTimedOut() {
		// if the current session has timed out, remove it
		// and create a new one
		s.removeSessionID()
		id, ok = s.generateSessionID()
		if !ok {
			return false
		}
		// set it as the new session
		s.setSessionID(id)
	}
	sessionKey := createKey(id, key)
    s.store.Set(sessionKey, val)
    s.updateLastActivityTime()
    //s.updateStoredKeys(key)
	return true
}

func (s *Session) Get(key string) (string, string) {
	id, ok := s.getSessionID()
	if !ok {
		return "", "Failed to find session ID"
	}
	if s.isTimedOut() {
		return "", "Session has timed out"
	}
	s.updateLastActivityTime()
	sessionKey := createKey(id, key)
    return s.store.Get(sessionKey)
}

func (s *Session) Delete(keys ...string) {
	id, ok := s.getSessionID()
	if !ok {
		return
	} 
	// TODO: check the logic of this
	if !s.isTimedOut() {
		s.updateLastActivityTime()
	}
	sessionKeys := []string{}
	for _, key := range keys {
		sessionKey := createKey(id, key)
		sessionKeys = append(sessionKeys, sessionKey)
	}
    s.store.Delete(sessionKeys...)

}

func (s *Session) Clear() {
	id, ok := s.getSessionID()
	if !ok {
		return
	}
    s.store.Remove(id)

}

//----------------------------------------------------------------------
//采取mongodb存储数据
//by indraw
type MongoStore struct {
	client *mgo.Collection
}
type MongoSession struct {
	S_id string
	S_time string
	S_keys string
}

func NewMongoStore(addr string) *MongoStore {
	con, err := mgo.Dial(addr)
    if err != nil {
        fmt.Println("Mongodb Error:", err)
        return nil
    }
    con.SetMode(mgo.Monotonic, true)
	db := con.DB("gobbs")
	return &MongoStore{client: db.C("session")}
}

func (s *MongoStore) Set(key string, val string) string{
	
	//result := make(bson.M)

	keyarr := strings.Split(key, ":")
	
	if len(keyarr) == 1{
		n,_ := s.client.Find(bson.M{"s_id": keyarr[0]}).Count()
		if n < 1{
			s.client.Insert(bson.M{"s_id": keyarr[0], "s_time": val});
			//fmt.Println("-mongo1:"+key)
		}
	}else if len(keyarr) == 2{
		//s.client.Insert(&MongoSession{k: key, v: val,t:exp});
		_ = s.client.Update(bson.M{"s_id": keyarr[0]}, bson.M{"$set": bson.M{keyarr[1]: val}})
		//fmt.Println("-mongo2:"+key)
	}
	return ""
}

func (s *MongoStore) Get(key string) (string, string) {
	//elem, err := s.client.Get(key)
	//return elem.String(), err
	keyarr := strings.Split(key, ":")

	//result := &MongoSession{}
	//result := &struct{ N string }{}
	//var re map[string]string
	result := make(map[string]string)
	//fmt.Println("-mongo5:"+key)

	s.client.Find(bson.M{"s_id": keyarr[0]}).Select(bson.M{keyarr[1]:1,"_id":0}).One(result)

	//s.client.Find(bson.M{"s_id": keyarr[0]}).One(result)
	
	//fmt.Printf("-mongo4:%s\n",keyarr[1])


	return result[keyarr[1]],""

}

func (s *MongoStore) Delete(keys ...string) {
	//s.client.Del(keys...)
	for _, v :=  range keys{
		keyarr := strings.Split(v, ":")
		s.client.Update(bson.M{"s_id": keyarr[0]},bson.M{"$unset": bson.M{keyarr[1]: 1}})
	}
}

func (s *MongoStore) Remove(id string) {
	//s.client.Del(keys...)
		s.client.Remove(bson.M{"s_id": id})
}

/*
type RedisStore struct {
	client *godis.Client
}

func NewRedisStore(addr string) *RedisStore {
	return &RedisStore{client: godis.New(addr, 0, "")}
}

func (s *RedisStore) Set(key string, val string, exp int64) string {
	
	 s.client.Setex(key, exp, val)
	
	return ""
}

func (s *RedisStore) Get(key string) (string, string) {
	elem, _ := s.client.Get(key)
	return elem.String(), ""
}

func (s *RedisStore) Delete(keys ...string) {
	s.client.Del(keys...)
}
*/
